package com.lam.generator.service.impl;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lam.common.base.domain.tree.BaseTree;
import com.lam.common.components.dict.DictCacheUtils;
import com.lam.common.constant.CommonConstant;
import com.lam.common.context.TokenUtils;
import com.lam.common.exception.BaseException;
import com.lam.common.exception.BusinessException;
import com.lam.common.exception.ParameterErrorException;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.StringUtils;
import com.lam.common.utils.file.FileUtil;
import com.lam.generator.common.util.GenUtils;
import com.lam.generator.common.util.RenderUtils;
import com.lam.generator.entity.DbTable;
import com.lam.generator.entity.DbTableColumn;
import com.lam.generator.entity.GenCodeModule;
import com.lam.generator.entity.GenTable;
import com.lam.generator.entity.GenTableColumn;
import com.lam.generator.entity.GenTableExtConfig;
import com.lam.generator.mapper.GenTableMapper;
import com.lam.generator.param.GenDbTableParam;
import com.lam.generator.param.GenTableParam;
import com.lam.generator.service.GenCodeModuleService;
import com.lam.generator.service.GenDbTableService;
import com.lam.generator.service.GenTableColumnService;
import com.lam.generator.service.GenTableExtConfigService;
import com.lam.generator.service.GenTableService;

/**
 * 业务 服务层实现
 * 
 * @author lam
 */
@Service
public class GenTableServiceImpl implements GenTableService {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private GenTableMapper genTableMapper;
	@Autowired
	private GenTableColumnService genTableColumnService;
	@Autowired
	private GenCodeModuleService genCodeModuleService;
	@Autowired
	private GenTableExtConfigService genTableExtConfigService;
	@Autowired
	private GenDbTableService genDbTableService;

    /**
     * 查询代码生成
     * 
     * @param id ID
     * @return 单个对象
     */
    @Override
    @Transactional(readOnly = true)
    public GenTable selectById(String id) {

    	return getGenTableWithColumn(id, true, false);
    }
    
    /**
     * 查询代码生成列表
     * 
     * @param param
     * @return 
     */
    @Override
    @Transactional(readOnly = true)
    public List<GenTable> selectList(GenTableParam param) {
    
        QueryWrapper<GenTable> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(param.getTableNameKey()), "table_name", param.getTableNameKey());
        queryWrapper.eq(StringUtils.isNotBlank(param.getTableName()), "table_name", param.getTableName());
        queryWrapper.like(StringUtils.isNotBlank(param.getTableCommentKey()), "table_comment", param.getTableCommentKey());
        queryWrapper.eq(StringUtils.isNotBlank(param.getTableComment()), "table_comment", param.getTableComment());

        return genTableMapper.selectList(queryWrapper);
    }

    /**
	 * 导入表结构
	 * @param tableList 导入表列表
	 */
	@Override
	@Transactional
	public void importGenTable(String tableNames) {
		
		// 查询表信息
		GenDbTableParam param = new GenDbTableParam();
		param.setTableNameList(StringUtils.str2List(tableNames));
		param.setFilterImport(true);
		List<DbTable> tableList = genDbTableService.selectTableList(param);
		if(CollectionUtil.isEmpty(tableList)) {
			throw new BusinessException("未找到数据表信息！");
		}
		
		String operName = TokenUtils.getLoginUsername();
		GenCodeModule codeModule;
		for (DbTable dbTable : tableList) {
			GenTable genTable = GenTable.create(dbTable);
			codeModule = genCodeModuleService.findModule(dbTable.getTableName());
			GenUtils.initTable(genTable, codeModule, operName);
			int row = genTableMapper.insert(genTable);
			if (row > 0) {
//				GenTableExtConfig tableExtConfig = new GenTableExtConfig();
//				tableExtConfig.setTableId(genTable.getId());
//				genTableExtConfigService.insert(tableExtConfig);
				// 保存列信息
				List<DbTableColumn> dbTableColumns = genDbTableService.selectColumnListByTable(genTable.getTableName());
				for (DbTableColumn dbColumn : dbTableColumns) {
					genTableColumnService.insert(GenUtils.initColumnField(GenTableColumn.create(dbColumn), genTable));
				}
			}
		}
	}
	
	@Override
	@Transactional
	public void syncFromDb(String tableId) {
		
		GenTable genTable = getGenTableWithColumn(tableId, false, false);
		if(genTable == null) {
			throw new ParameterErrorException("数据表已不存在，请刷新列表重试！");
		}
		DbTable dbTable = genDbTableService.selectTableByName(genTable.getTableName());
		if(dbTable == null) {
			throw new ParameterErrorException("数据库中该表已不存在！");
		}
		
		List<GenTableColumn> genTableColumnList = genTable.getColumns();
		Map<String, GenTableColumn> genTableColumnMap = CollectionUtil.getMap(genTableColumnList, GenTableColumn::getColumnName);
		
		GenCodeModule codeModule = genCodeModuleService.findModule(dbTable.getTableName());
		GenUtils.setCodeModule(genTable, codeModule);
		genTable.setTableComment(dbTable.getTableComment());//更新表注释
		genTableMapper.updateById(genTable);
		
		List<DbTableColumn> dbTableColumnList = genDbTableService.selectColumnListByTable(genTable.getTableName());
		Map<String, DbTableColumn> dbTableColumnMap = CollectionUtil.getMap(dbTableColumnList, DbTableColumn::getColumnName);
		
		for (DbTableColumn dbTableColumn : dbTableColumnList) {
			GenTableColumn genTableColumn = genTableColumnMap.get(dbTableColumn.getColumnName());
			if(genTableColumn == null) {
				genTableColumnService.insert(GenUtils.initColumnField(GenTableColumn.create(dbTableColumn), genTable));
			}else {
				if(genTableColumn.merge(dbTableColumn)) {
					GenUtils.initColumnField(genTableColumn, genTable);
				}
				genTableColumnService.update(genTableColumn);
			}
		}
		
		List<String> idList = new ArrayList<>();
		for (GenTableColumn genTableColumn : genTableColumnList) {
			if(!dbTableColumnMap.containsKey(genTableColumn.getColumnName())) {
				idList.add(genTableColumn.getId());
			}
		}
		if(!idList.isEmpty()) {
			genTableColumnService.deleteById(idList);
		}
	}

    /**
     * 修改代码生成
     * @param genTable 
     * @return 
     */
    @Override
    @Transactional
    public int update(GenTable genTable) {
    
        genTable.setOptions(JsonUtil.toJsonString(genTable.getOptionObject()));
        if(genTable.isSimpleTree() && StringUtils.isBlank(genTable.getParentClassName())) {
			genTable.setParentClass(BaseTree.class);
			genTable.setParentClassName(BaseTree.class.getName());
		}
        
        GenTableExtConfig extConfig = genTable.getExtConfig();
        GenCodeModule codeModule = genCodeModuleService.selectById(genTable.getCodeModuleId());
		GenTableExtConfig tableExtConfig = genTableExtConfigService.selectByTableId(genTable.getId());
		if(tableExtConfig == null) {
			genTableExtConfigService.insert(GenUtils.diffTableExtConfig(codeModule, extConfig));
		}else {
			extConfig.setId(tableExtConfig.getId());
			genTableExtConfigService.update(GenUtils.diffTableExtConfig(codeModule, extConfig));
		}
		
		int row = genTableMapper.updateById(genTable);
		if (row > 0) {
			for (GenTableColumn column : genTable.getColumns()) {
				if(StringUtils.isNotBlank(column.getDictText())) {
					if (StringUtils.isNotBlank(column.getDictCode())) {
						String[] ss = column.getDictText().split(" ");
						if(!StringUtils.equals(ss[0], column.getDictCode()) ) {
							column.setDictText(column.getDictCode() + " " + ss[1]);
						}
					}else {
						column.setDictCode(column.getDictText().split(" ")[0]);
					}
				}
				
				genTableColumnService.update(column);
			}
		}
		return row;
    }

    /**
     * 删除代码生成信息
     * 
     * @param idStr ID，多个用逗号分隔
     * @return 
     */
    @Override
    @Transactional
    public int deleteById(String idStr) {
    	
    	List<String> tableIdList = StringUtils.str2List(idStr);
    	int n = genTableMapper.deleteBatchIds(tableIdList);
		genTableColumnService.deleteByTableId(idStr);
        return n;
    }
    
	private GenTable getGenTableWithColumn(String tableId, boolean useCodeModule, boolean checkCodeModule) {
		GenTable genTable = genTableMapper.selectById(tableId);
		if(genTable == null) {
			return null;
		}
		
		if(useCodeModule) {
			if(StringUtils.isBlank(genTable.getCodeModuleId())) {
				if(checkCodeModule) {
					logger.error("生成代码时检查到没有模块信息！tableName:{}", genTable.getTableName());
					throw new BaseException("生成代码时检查到没有模块信息！");
				}
				return null;
			}
			GenCodeModule codeModule = genCodeModuleService.selectById(genTable.getCodeModuleId());
			GenTableExtConfig tableExtConfig = genTableExtConfigService.selectByTableId(genTable.getId());
			genTable.setCodeModule(GenUtils.mergeTableExtConfig(codeModule, tableExtConfig));
			if(checkCodeModule && genTable.getCodeModule() == null) {
				logger.error("生成代码时没有查询模块信息！tableName:{}", genTable.getTableName());
				throw new BaseException("生成代码时没有查询模块信息！");
			}
		}
		// 设置代码生成其他选项值
		genTable.setOptionObject(StringUtils.isBlank(genTable.getOptions())?Collections.emptyMap():JsonUtil.parseJSON(genTable.getOptions()));
		genTable.setColumns(genTableColumnService.selectListByTableId(genTable.getId()));
		return genTable;
	}
	
	/**
	 * 预览代码
	 * 
	 * @param tableId 表编号
	 * @return 预览数据列表
	 */
	@Override
	public Map<String, String> previewCode(String tableId) {
		GenTable table = getGenTableWithColumn(tableId, true, true);
		Map<String, String> templateCodeMap = RenderUtils.generatorCode(table);
		createDictSql(templateCodeMap, table);
		
		Map<String, String> codeMap = new LinkedHashMap<>();
		for (Entry<String, String> entry : templateCodeMap.entrySet()) {
			codeMap.put(FileUtil.getFileNameFromPath(entry.getKey()), entry.getValue());
		}
		return codeMap;
	}

	/**
	 * 生成代码（自定义路径）
	 * 
	 * @param tableId 表id
	 * @return 数据
	 */
	@Override
	public int generatorCode2Project(String tableId) {
		
		// 查询表信息
		GenTable table = getGenTableWithColumn(tableId, true, true);
		Map<String, String> templateCodeMap = RenderUtils.generatorCode(table);
		
		String basePath = GenUtils.getGenBasePath(table.getGenPath());
		File file;
		int n = 0;
		for (Entry<String, String> code : templateCodeMap.entrySet()) {
			if(StringUtils.containsAny(code.getKey(), "other.java", "sql.sql")) {
				continue;
			}
			try {
				file = new File(basePath + RenderUtils.getFilePath(code.getKey(), table));
				logger.info("开始生产代码文件 -> {}", file.getAbsolutePath());
				FileUtils.writeStringToFile(file, code.getValue(), "utf-8");
				n++;
			} catch (IOException e) {
				throw new BusinessException("生成代码到项目失败，表名：" + table.getTableName());
			}
		}
		return n;
	}

	/**
	 * 生成代码（下载方式）
	 * 
	 * @param tableIds 表数组
	 * @return 数据
	 */
	@Override
	public byte[] downloadCode(String tableIds) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		for (String id : tableIds.split(",")) {
			generatorCode(id, zip);
		}
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	private String createDictSql(Map<String, String> templateCodeMap, GenTable table) {

		String sqlTemplate = "sql/sql.sql";
		String templateContent = templateCodeMap.get(sqlTemplate);
		if (templateContent == null) {
			templateContent = "";
		}

		StringBuilder sb = new StringBuilder();
		for (GenTableColumn c : table.getColumns()) {
			if (StringUtils.isBlank(c.getDictText())) {
				continue;
			}
			
			String dictCode = c.getDictCode();
			String[] ss = c.getDictText().split(" ");
			if(StringUtils.isNotBlank(dictCode) && !StringUtils.equals(dictCode, ss[0])) {
				continue;
			}
			
			Map<String, String> dictMap = StringUtils.isBlank(dictCode) ? null : DictCacheUtils.getDictMap(dictCode);
			dictCode = ss[0];
			if(!DictCacheUtils.getDictService().existDict(dictCode)) {
				//字典记录
				sb.append("-- 新增字典 ").append(dictCode).append(" \n");
				String remark = c.getColumnComment().replace(c.getFormItemLabel(), "").replace("（", "").replace("）", "").replace("(", "").replace(")", "");
				sb.append(String.format("INSERT INTO sys_dict(dict_name, dict_code, description, type, status, create_by, create_time, update_by, update_time) VALUES ('%s', '%s', '%s', 2, 1, '%s', now(), NULL, NULL);%n",
						c.getFormItemLabel(), dictCode, remark, table.getFunctionAuthor()));
			}
			
			StringBuilder itemSb = new StringBuilder();
			String[] vs = ss[1].split(",");
			for (int i = 0; i < vs.length; i++) {
				String[] entry = vs[i].split("=");
				if(CollectionUtil.isNotEmpty(dictMap) && dictMap.containsKey(entry[0])) {
					continue;
				}
				//字典项记录
				itemSb.append(String.format("INSERT INTO sys_dict_item(dict_id, item_text, item_value, description, sort_order, status, create_by, create_time, update_by, update_time) VALUES (@dictId, '%s', '%s', '%s', '%s', 1,  '%s', now(), NULL, NULL);%n",
						entry[1], entry[0], entry[1], i, table.getFunctionAuthor()));
			}
			if(itemSb.length() > 0) {
				sb.append("-- 字典ID\n");
				sb.append("SELECT @dictId := (SELECT id FROM sys_dict where dict_code='"+dictCode+"');\n");
				sb.append("-- 字典 ").append(dictCode).append(" 的项\n");
				sb.append(itemSb);
				sb.append("\n\n");
			}
		}
		
		if (sb.length() > 0) {
			templateContent += "\n\n" + sb.toString() + "\n";
			templateCodeMap.put(sqlTemplate, templateContent);
		}

		return templateContent;
	}

	/**
	 * 查询表信息并生成代码
	 */
	private void generatorCode(String tableId, ZipOutputStream zip) {
		// 查询表信息
		GenTable table = getGenTableWithColumn(tableId, true, true);
		Map<String, String> templateCodeMap = RenderUtils.generatorCode(table);
		createDictSql(templateCodeMap, table);
		for (Entry<String, String> code : templateCodeMap.entrySet()) {
			try {
				// 添加到zip
				zip.putNextEntry(new ZipEntry(RenderUtils.getFilePath(code.getKey(), table)));
				IOUtils.write(code.getValue(), zip, CommonConstant.UTF8);
				zip.flush();
				zip.closeEntry();
			} catch (IOException e) {
				logger.error("生成代码添加压缩包失败，表名：" + table.getTableName(), e);
			}
		}
	}

}